Webpack 性能优化

分析问题

  1. 未优化前构建需要多少时间,优化后构建需要多少时间?
  2. 有没有分析是什么问题导致构建速率变慢?

优化方面

从两个方面考虑:

  1. 有哪些方式可以减少 Webpack 的打包时间
  2. 有哪些方式可以让 Webpack 打出来的包更小. 优化这一点为主。

优化 webpack 打包速度

生产模式:

  • 关闭 sourcemap (选择)
  • webpack 配置给 optimization: { minimize: true }
    • build 环境下一般会对输出的 js 文件再进一步所压缩处理(但是会丢失 sourcemap, 线上出问题无法及时定位)
    • 压缩只用于生产阶段.
  • 添加 externals 分离出一些大的包,比如 vue
  • 去 console 提升性能 (webpack 插件)

开发模式:

  • 按需打包
  • 构建优化:减少编译体积 ContextReplacementPlugin、IgnorePlugin、babel-plugin-import、babel-plugin-transform-runtime。
  • 将变动很少的模块划分出 webpack 的主 bundlejs
  • babel-loader ts(x) 处理之后上层加一层 cache-loader 用来缓存, 缓存 cache-loader、hard-source-webpack-plugin、uglifyjsWebpackPlugin 开启缓存、babel-loader 开启缓存
  • 预编译, 使用 dll 插件优化打包时间。 DllPlugin + DllReferencePlugin
  • HappyPack 开启多进程编译,但是也并不一定支持所有 loader 都适合。thread-loader、parallel-webpack、thread-loader、uglifyjsWebpackPlugin 开启并行
  • 开发环境去除所有跟 minimize, chunk 之类的配置
  • UglifyJsPlugin 压缩很慢,如何提高速度? 缓存原理,压缩只重新压缩改变的,还有就是减少冗余的代码,

性能优化

  1. 减少编译体积 Tree-shaking、Scope Hositing。
  2. hash 缓存 webpack-md5-plugin
  3. 拆包 splitChunksPlugin、import()、require.ensure

优化 Loader

将 Babel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间

loader: "babel-loader?cacheDirectory=true";

cache-loader

在性能开销较大的 loader 前面使用这个 loader 能够缓存住上一次的结果

HappyPack

受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。

HappyPack 可以将 Loader 的同步执行转换为并行的

module.exports = {
  module: {
    loaders: [
      {
        test: /\.js$/,
        include: [resolve("src")],
        exclude: /node_modules/,
        // id 后面的内容对应下面
        loader: "happypack/loader?id=happybabel",
      },
    ],
  },
  plugins: [
    new HappyPack({
      id: "happybabel",
      loaders: ["babel-loader?cacheDirectory"],
      // 开启 4 个线程
      threads: 4,
    }),
  ],
};

DllPlugin

DllPlugin 可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。

接下来我们就来学习如何使用 DllPlugin

// 单独配置在一个文件中
// webpack.dll.conf.js
const path = require("path");
const webpack = require("webpack");

module.exports = {
  entry: {
    // 想统一打包的类库
    vendor: ["react"],
  },
  output: {
    path: path.join(__dirname, "dist"),
    filename: "[name].dll.js",
    library: "[name]-[hash]",
  },
  plugins: [
    new webpack.DllPlugin({
      // name 必须和 output.library 一致
      name: "[name]-[hash]",
      // 该属性需要与 DllReferencePlugin 中一致
      context: __dirname,
      path: path.join(__dirname, "dist", "[name]-manifest.json"),
    }),
  ],
};

然后我们需要执行这个配置文件生成依赖文件,接下来我们需要使用 DllReferencePlugin 将依赖文件引入项目中

// webpack.conf.js
module.exports = {
  // ...省略其他配置
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      // manifest 就是之前打包出来的 json 文件
      manifest: require("./dist/vendor-manifest.json"),
    }),
  ],
};

代码压缩

在 Webpack4 中,我们就不需要以上这些操作了,只需要将 mode 设置为 production 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。

多页面提取公共资源

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          chunks: "initial",
          minChunks: 2, //最小重复的次数
          minSize: 0, //最小提取字节数
        },
        vendor: {
          test: /node_modules/,
          chunks: "initial",
          name: "vendor",
        },
      },
    },
  },
};

webpack 4.6.0+增加了对预取和预加载的支持。

动态导入:注释中的使用 webpackChunkName,可以单独给 bundle 命名,lodash.bundle.js 而不是 [id].bundle.js。

import(/* webpackChunkName: "lodash" */ "lodash");

预取(prefetch):将来可能需要一些导航资源

  • 只要父chunk加载完成,webpack就会添加 prefetch
import(/* webpackPrefetch: true */ "LoginModal");

// 将<link rel="prefetch" href="login-modal-chunk.js">其附加在页面的开头

预加载(preload):当前导航期间可能需要资源

  • preload chunk 会在父 chunk 加载时,以并行方式开始加载
  • 不正确地使用 webpackPreload 会有损性能,
import(/* webpackPreload: true */ "ChartingLibrary");

// 在加载父 chunk 的同时
// 还会通过 <link rel="preload"> 请求 charting-library-chunk
HappyPack

HappyPackopen in new window可以开启多进程 Loader 转换,将任务分解给多个子进程,最后将结果发给主进程。

使用

exports.plugins = [
  new HappyPack({
    id: "jsx",
    threads: 4,
    loaders: ["babel-loader"],
  }),

  new HappyPack({
    id: "styles",
    threads: 2,
    loaders: ["style-loader", "css-loader", "less-loader"],
  }),
];

exports.module.rules = [
  {
    test: /\.js$/,
    use: "happypack/loader?id=jsx",
  },

  {
    test: /\.less$/,
    use: "happypack/loader?id=styles",
  },
];
ParallelUglifyPlugin

ParallelUglifyPluginopen in new window可以开启多进程压缩 JS 文件

import ParallelUglifyPlugin from "webpack-parallel-uglify-plugin";

module.exports = {
  plugins: [
    new ParallelUglifyPlugin({
      test,
      include,
      exclude,
      cacheDir,
      workerCount,
      sourceMap,
      uglifyJS: {},
      uglifyES: {},
    }),
  ],
};
Last Updated:
Contributors: yiliang114